/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_model.cpp - models format loading
//


#include "r_local.h"


#define MODELS_HASH_SIZE				(MAX_MODELS >> 2)

static model_t *			r_modelsHashTable[MODELS_HASH_SIZE];
static model_t *			r_models[MAX_MODELS];
static int					r_numModels;


/*
 ==================
 R_CalcTangents
 ==================
*/
static void R_CalcTangents (int numTriangles, aliasMdlTriangle_t *triangles, int numVertices, aliasMdlVertex_t *vertices, aliasMdlSt_t *st, int numFrames){

	aliasMdlTriangle_t	*triangle;
	aliasMdlVertex_t	*vertex, *v0, *v1, *v2;
	aliasMdlSt_t		*vSt0, *vSt1, *vSt2;
	glqVec5				edges[2];
	glqVec3				normal, tangents[2];
	glqVec3				cross;
	int					i;

	vertices += numVertices * numFrames;

	// Clear
	for (i = 0, vertex = vertices; i < numVertices; i++, vertex++){
		vertex->tangents[0].Clear();
		vertex->tangents[1].Clear();
	}

	// Calculate tangent vectors
	for (i = 0, triangle = triangles; i < numTriangles; i++, triangle++){
		v0 = &vertices[triangle->index[0]];
		v1 = &vertices[triangle->index[1]];
		v2 = &vertices[triangle->index[2]];

		vSt0 = &st[triangle->index[0]];
		vSt1 = &st[triangle->index[1]];
		vSt2 = &st[triangle->index[2]];

		// Find edges
		edges[0][0] = v1->xyz[0] - v0->xyz[0];
		edges[0][1] = v1->xyz[1] - v0->xyz[1];
		edges[0][2] = v1->xyz[2] - v0->xyz[2];
		edges[0][3] = vSt1->st[0] - vSt0->st[0];
		edges[0][4] = vSt1->st[1] - vSt0->st[1];

		edges[1][0] = v2->xyz[0] - v0->xyz[0];
		edges[1][1] = v2->xyz[1] - v0->xyz[1];
		edges[1][2] = v2->xyz[2] - v0->xyz[2];
		edges[1][3] = vSt2->st[0] - vSt0->st[0];
		edges[1][4] = vSt2->st[1] - vSt0->st[1];

		// Compute normal vector
		normal[0] = edges[1][1] * edges[0][2] - edges[1][2] * edges[0][1];
		normal[1] = edges[1][2] * edges[0][0] - edges[1][0] * edges[0][2];
		normal[2] = edges[1][0] * edges[0][1] - edges[1][1] * edges[0][0];

		normal.Normalize();

		// Compute first tangent vector
		tangents[0][0] = edges[1][4] * edges[0][0] - edges[1][0] * edges[0][4];
		tangents[0][1] = edges[1][4] * edges[0][1] - edges[1][1] * edges[0][4];
		tangents[0][2] = edges[1][4] * edges[0][2] - edges[1][2] * edges[0][4];

		tangents[0].Normalize();

		// Compute second tangent vector
		tangents[1][0] = edges[1][0] * edges[0][3] - edges[1][3] * edges[0][0];
		tangents[1][1] = edges[1][1] * edges[0][3] - edges[1][3] * edges[0][1];
		tangents[1][2] = edges[1][2] * edges[0][3] - edges[1][3] * edges[0][2];

		tangents[1].Normalize();

		// Inverse tangent vectors if needed
		cross.Cross(tangents[1], tangents[0]);
		if (cross.Dot(normal) < 0.0f){
			tangents[0] = -tangents[0];
			tangents[1] = -tangents[1];
		}

		// Add the vectors
		v0->tangents[0] += tangents[0];
		v1->tangents[0] += tangents[0];
		v2->tangents[0] += tangents[0];

		v0->tangents[1] += tangents[1];
		v1->tangents[1] += tangents[1];
		v2->tangents[1] += tangents[1];
	}

	// Renormalize
	for (i = 0, vertex = vertices; i < numVertices; i++, vertex++){
		vertex->tangents[0].Normalize();
		vertex->tangents[1].Normalize();
	}
}

/*
 ==================
 R_CalcFacePlanes
 ==================
*/
static void R_CalcFacePlanes (int numTriangles, aliasMdlTriangle_t *triangles, aliasMdlVertex_t *vertices, aliasMdlFacePlane_t *facePlanes){

	aliasMdlTriangle_t	*triangle;
	aliasMdlVertex_t	*v0, *v1, *v2;
	glqVec3				edges[2];
	int					i;

	// Calculate face planes
	for (i = 0, triangle = triangles; i < numTriangles; i++, triangle++){
		v0 = &vertices[triangle->index[0]];
		v1 = &vertices[triangle->index[1]];
		v2 = &vertices[triangle->index[2]];

		edges[0] = v1->xyz - v0->xyz;
		edges[1] = v2->xyz - v0->xyz;

		// Compute plane normal
		facePlanes[i].normal.Cross(edges[1], edges[0]);
		facePlanes[i].normal.Normalize();

		// Compute plane distance
		facePlanes[i].dist = v0->xyz.Dot(facePlanes[i].normal);
	}
}

/*
 ==================

 ==================
*/
static void R_CacheModelGeometry (aliasMdl_t *model, const char *modelName){

}


/*
 ==============================================================================

 .MDL FORMAT LOADING

 ==============================================================================
*/


/*
 ==================
 R_LoadSingleSkin
 ==================
*/
static void *R_LoadSingleSkin (mdlHeader_t *inHeader, mdlSkin_t *inSkin, aliasMdlMaterial_t *outMaterial, int numMaterials, const char *name){

	char	checkName[MAX_PATH_LENGTH], loadName[MAX_PATH_LENGTH];
	int		size;
	int		i;

	size = inHeader->skinWidth * inHeader->skinHeight;

	Str_Copy(checkName, name, sizeof(checkName));
	Str_StripFileExtension(checkName);

	for (i = 0; i < numMaterials; i++){
		// Load the texture
		Str_SPrintf(loadName, sizeof(loadName), "%s_%i", checkName, i);
		R_FindLumpTexture(loadName, TF_NOPICMIP, TF_DEFAULT, TW_REPEAT, inHeader->skinWidth, inHeader->skinHeight, (byte *)(inSkin + 1));

		// Load the material
		outMaterial->material = R_FindMaterial(loadName, MT_GENERIC, SURFACEPARM_LIGHTING);

		// Go to the next skin
		inSkin = (mdlSkin_t *)((byte *)(inSkin + 1) + size);
	}

	return (void *)inSkin;
}

/*
 ==================

 ==================
*/
static void *R_LoadGroupSkin (){

	return NULL;
}

/*
 ==================
 R_LoadSingleFrame
 ==================
*/
static void *R_LoadSingleFrame (aliasMdl_t *outModel, int *size, void *inFrame, aliasMdlVertex_t *outVertex, aliasMdlSurface_t *outSurface, aliasMdlFrame_t *outFrame, glqVec3 scale, glqVec3 translate){

	mdlSingleFrame_t	*inSingleFrame;
	mdlVertex_t			*inVertex;
	int					i;

	// Load the single frame
	inSingleFrame = (mdlSingleFrame_t *)inFrame;

	// Load the vertices
	inVertex = (mdlVertex_t *)(inSingleFrame + 1);

	for (i = 0; i < outSurface->numVertices; i++){
		outVertex[i].xyz[0] = (float)inVertex[i].xyz[0] * scale[0] + translate[0];
		outVertex[i].xyz[1] = (float)inVertex[i].xyz[1] * scale[1] + translate[1];
		outVertex[i].xyz[2] = (float)inVertex[i].xyz[2] * scale[2] + translate[2];

		glqMath::ByteToDir(inVertex[i].normalIndex, outVertex[i].normal);
	}

	// Load the bounds
	outFrame->bounds[0] = translate;
	outFrame->bounds[1] = translate + scale * 255.0f;

	// Load the sphere
	outFrame->sphere = outFrame->bounds.ToSphere();

	// Go to the next vertex
	inVertex += outSurface->numVertices;

	return (void *)inVertex;
}

/*
 ==================
 R_LoadGroupFrame

 FIXME: this does not animate
 ==================
*/
static void *R_LoadGroupFrame (aliasMdl_t *outModel, int *size, void *inFrame, aliasMdlVertex_t *outVertex, aliasMdlSurface_t *outSurface, aliasMdlFrame_t *outFrame, glqVec3 scale, glqVec3 translate){

	mdlGroupFrame_t	*inGroupFrame;
	mdlInterval_t	*inInterval;
	mdlVertex_t		*inVertex;
	void			*ptr;
	int				i, j;

	// Load the group frame
	inGroupFrame = (mdlGroupFrame_t *)inFrame;

	// Load the interval
	inInterval = (mdlInterval_t *)(inGroupFrame + 1);

	inInterval += inGroupFrame->numFrames;

	// Set the data ptr offset
	ptr = (void *)inInterval;

	// Load the vertices
	for (i = 0; i < inGroupFrame->numFrames; i++){
		inVertex = (mdlVertex_t *)((mdlSingleFrame_t *)ptr + 1);

		for (j = 0; j < outSurface->numVertices; j++){
			outVertex[j].xyz[0] = (float)inVertex[j].xyz[0] * scale[0] + translate[0];
			outVertex[j].xyz[1] = (float)inVertex[j].xyz[1] * scale[1] + translate[1];
			outVertex[j].xyz[2] = (float)inVertex[j].xyz[2] * scale[2] + translate[2];

			glqMath::ByteToDir(inVertex[j].normalIndex, outVertex[j].normal);
		}

		// Load the bounds
		outFrame->bounds[0] = translate;
		outFrame->bounds[1] = translate + scale * 255.0f;

		// Load the sphere
		outFrame->sphere = outFrame->bounds.ToSphere();

		// Go to the next group
		ptr = (mdlVertex_t *)((mdlSingleFrame_t *)ptr + 1) + outSurface->numVertices;
	}

	return ptr;
}

/*
 ==================
 R_LoadMDL

 FIXME: texture coordinates are fucked up
 ==================
*/
static bool R_LoadMDL (const char *name, aliasMdl_t **model, int *size){

	byte				*data;
	mdlHeader_t			*inHeader;
	mdlSkin_t			*inSkin;
	mdlSt_t				*inSt;
	mdlTriangle_t		*inTriangle;
	mdlFrame_t			*inFrame;
	aliasMdl_t			*outModel;
	aliasMdlSurface_t	*outSurface;
	aliasMdlMaterial_t	*outMaterial;
	aliasMdlSt_t		*outSt;
	aliasMdlTriangle_t	*outTriangle;
	aliasMdlFrame_t		*outFrame;
	aliasMdlVertex_t	*outVertex;
	int					id, version;
	float				skinWidth, skinHeight;
	glqVec3				scale, translate;
	int					numSkins;
	int					numVertices;
	int					numTriangles;
	int					numFrames;
	int					i, j;

	// Load the file
	FS_ReadFile(name, (void **)&data);
	if (!data)
		return false;

	*model = outModel = (aliasMdl_t *)Mem_Alloc(sizeof(aliasMdl_t), TAG_RENDERER);
	*size = sizeof(aliasMdl_t);

	// Byte swap the header fields and sanity check
	inHeader = (mdlHeader_t *)data;

	id = LittleLong(inHeader->id);
	if (id != MDL_ID)
		Com_Error(false, "R_LoadMDL: wrong file id (%s)", name);

	version = LittleLong(inHeader->version);
	if (version != MDL_VERSION)
		Com_Error(false, "R_LoadMDL: wrong version number (%i should be %i) (%s)", version, MDL_VERSION, name);

	// Check the number of tags
	outModel->numTags = 0;

	// Check the number of surfaces
	outModel->numSurfaces = 1;

	// Load the surfaces
	outModel->surfaces = outSurface = (aliasMdlSurface_t *)Mem_Alloc(sizeof(aliasMdlSurface_t), TAG_RENDERER);
	*size = sizeof(aliasMdlSurface_t);

	// Load the scale and translate
	scale[0] = LittleFloat(inHeader->scale[0]);
	scale[1] = LittleFloat(inHeader->scale[1]);
	scale[2] = LittleFloat(inHeader->scale[2]);

	translate[0] = LittleFloat(inHeader->translate[0]);
	translate[1] = LittleFloat(inHeader->translate[1]);
	translate[2] = LittleFloat(inHeader->translate[2]);

	// Check the skin width
	skinWidth = (float)LittleLong(inHeader->skinWidth);
	if (skinWidth <= 0)
		Com_Error(false, "R_LoadMDL: too small skin width (%s)", name);
	else if (skinWidth > MDL_MAX_WIDTH)
		Com_Error(false, "R_LoadMDL: too large skin height (%s)", name);

	skinWidth = 1.0f / skinWidth;

	// Check the skin height
	skinHeight = (float)LittleLong(inHeader->skinHeight);
	if (skinHeight <= 0)
		Com_Error(false, "R_LoadMDL: too small skin height (%s)", name);
	else if (skinHeight > MDL_MAX_HEIGHT)
		Com_Error(false, "R_LoadMDL: too large skin height (%s)", name);

	skinHeight = 1.0f / skinHeight;

	// Check the number of skins
	numSkins = LittleLong(inHeader->numSkins);

	if (numSkins < 0)
		Com_Error(false, "R_LoadMDL: no skins (%s)", name);
	else if (numSkins > MDL_MAX_SKINS)
		Com_Error(false, "R_LoadMDL: too many skins (%s)", name);

	outSurface->numMaterials = numSkins;

	// Load the skins
	inSkin = (mdlSkin_t *)&inHeader[1];

	outSurface->materials = outMaterial = (aliasMdlMaterial_t *)Mem_Alloc(outSurface->numMaterials * sizeof(aliasMdlMaterial_t), TAG_RENDERER);
	*size += outSurface->numMaterials * sizeof(aliasMdlMaterial_t);

	switch (LittleLong(inSkin->type)){
	case MDL_SKIN_SINGLE:
		inSkin = (mdlSkin_t *)R_LoadSingleSkin(inHeader, inSkin, outMaterial, outSurface->numMaterials, name);
		break;
	case MDL_SKIN_GROUP:
		inSkin = (mdlSkin_t *)R_LoadGroupSkin();
		break;
	default:
		Com_Error(false, "R_LoadMDL: bad skin type (%s)", name);
	}

	// Check the number of vertices
	numVertices = LittleLong(inHeader->numVertices);
	if (numVertices < 1)
		Com_Error(false, "R_LoadMDL: no vertices (%s)", name);
	else if (numVertices > MDL_MAX_VERTICES)
		Com_Error(false, "R_LoadMDL: too many vertices (%s)", name);

	outSurface->numVertices = numVertices;

	// Load the S and T vertices
	inSt = (mdlSt_t *)inSkin;

	outSurface->st = outSt = (aliasMdlSt_t *)Mem_Alloc(outSurface->numVertices * sizeof(aliasMdlSt_t), TAG_RENDERER);
	*size += outSurface->numVertices * sizeof(aliasMdlSt_t);

	for (i = 0; i < outSurface->numVertices; i++, outSt++){
		outSt->onSeam = LittleLong(inSt[i].onSeam);

		outSt->st[0] = ((float)LittleLong(inSt[i].s) + 0.5) * skinWidth;
		outSt->st[1] = ((float)LittleLong(inSt[i].t) + 0.5) * skinHeight;
	}

	// Check the number of triangles
	numTriangles = LittleLong(inHeader->numTriangles);
	if (numTriangles < 1)
		Com_Error(false, "R_LoadMDL: no triangles (%s)", name);
	else if (numTriangles > MDL_MAX_TRIANGLES)
		Com_Error(false, "R_LoadMDL: too many triangles (%s)", name);

	outSurface->numTriangles = numTriangles;

	// Load the triangles
	inTriangle = (mdlTriangle_t *)&inSt[outSurface->numVertices];

	outSurface->triangles = outTriangle = (aliasMdlTriangle_t *)Mem_Alloc(outSurface->numTriangles * sizeof(aliasMdlTriangle_t), TAG_RENDERER);
	*size += outSurface->numTriangles * sizeof(aliasMdlTriangle_t);

	for (i = 0; i < outSurface->numTriangles; i++, outTriangle++){
		outTriangle->faceFront = LittleLong(inTriangle[i].faceFront);

		outTriangle->index[0] = (glIndex_t)LittleLong(inTriangle[i].index[0]);
		outTriangle->index[1] = (glIndex_t)LittleLong(inTriangle[i].index[1]);
		outTriangle->index[2] = (glIndex_t)LittleLong(inTriangle[i].index[2]);
	}

	// Correct texture coordinates
	for (i = 0; i < outSurface->numTriangles; i++){
		for (j = 0; j < 3; j++){
			if (!outSurface->triangles[i].faceFront && outSurface->st[outSurface->triangles[i].index[j]].onSeam)
				outSurface->st[outSurface->triangles[i].index[j]].st[0] += skinWidth * 0.5f;
		}
	}

	// Allocate the face planes
	outSurface->facePlanes = (aliasMdlFacePlane_t *)Mem_Alloc(outSurface->numTriangles * sizeof(aliasMdlFacePlane_t), TAG_RENDERER);

	// Check the number of frames
	numFrames = LittleLong(inHeader->numFrames);
	if (numFrames < 1)
		Com_Error(false, "R_LoadMDL: no frames (%s)", name);
	else if (numFrames > MDL_MAX_FRAMES)
		Com_Error(false, "R_LoadMDL: too many frames (%s)", name);

	outModel->numFrames = numFrames;

	// Load the frames
	inFrame = (mdlFrame_t *)&inTriangle[outSurface->numTriangles];

	outModel->frames = outFrame = (aliasMdlFrame_t *)Mem_Alloc(outModel->numFrames * sizeof(aliasMdlFrame_t), TAG_RENDERER);
	*size += outModel->numFrames * sizeof(aliasMdlFrame_t);

	outSurface->vertices = outVertex = (aliasMdlVertex_t *)Mem_Alloc(outModel->numFrames * outSurface->numVertices * sizeof(aliasMdlVertex_t), TAG_RENDERER);
	*size += outModel->numFrames * outSurface->numVertices * sizeof(aliasMdlVertex_t);

	for (i = 0; i < outModel->numFrames; i++, outFrame++, outVertex += outSurface->numVertices){
		switch (inFrame->type){
		case MDL_FRAME_SINGLE:
			inFrame = (mdlFrame_t *)R_LoadSingleFrame(outModel, size, inFrame + 1, outVertex, outSurface, outFrame, scale, translate);
			break;
		case MDL_FRAME_GROUP:
			inFrame = (mdlFrame_t *)R_LoadGroupFrame(outModel, size, inFrame + 1, outVertex, outSurface, outFrame, scale, translate);
			break;
		default:
			Com_Error(false, "R_LoadMDL: bad frame type (%s)", name);
		}

		// Calculate tangent vectors
		R_CalcTangents(outSurface->numTriangles, outSurface->triangles, outSurface->numVertices, outSurface->vertices, outSurface->st, i);

		// Calculate face planes
		R_CalcFacePlanes(outSurface->numTriangles, outSurface->triangles, outSurface->vertices, outSurface->facePlanes);
	}

	// Clear index and vertex buffers
	outSurface->indexBuffer = NULL;
	outSurface->indexOffset = 0;

	outSurface->vertexBuffer = NULL;
	outSurface->vertexOffset = 0;

	// Free file data
	FS_FreeFile(data);

	// Try to cache the geometry in static index and vertex buffers
	R_CacheModelGeometry(outModel, name);

	return true;
}


/*
 ==============================================================================

 .MD3 FORMAT LOADING

 ==============================================================================
*/


/*
 ==================
 R_LoadMD3
 ==================
*/
static bool R_LoadMD3 (const char *name, aliasMdl_t **model, int *size){

	byte				*data;
	md3Header_t			*inHeader;
	md3Frame_t			*inFrame;
	md3Tag_t			*inTag;
	md3Surface_t		*inSurface;
	md3Material_t		*inMaterial;
	md3Triangle_t		*inTriangle;
	md3St_t				*inSt;
	md3Vertex_t			*inVertex;
	aliasMdl_t			*outModel;
	aliasMdlFrame_t		*outFrame;
	aliasMdlTag_t		*outTag;
	aliasMdlSurface_t	*outSurface;
	aliasMdlMaterial_t	*outMaterial;
	aliasMdlTriangle_t	*outTriangle;
	aliasMdlSt_t		*outSt;
	aliasMdlVertex_t	*outVertex;
	char				materialName[MAX_PATH_LENGTH];
	glqVec3				scale, translate;
	float				lat, lng;
	int					id, version;
	int					numFrames;
	int					numTags;
	int					numSurfaces;
	int					numMaterials;
	int					numTriangles;
	int					numVertices;
	int					i, j, k;

	// Load the file
	FS_ReadFile(name, (void **)&data);
	if (!data)
		return false;

	*model = outModel = (aliasMdl_t *)Mem_Alloc(sizeof(aliasMdl_t), TAG_RENDERER);
	*size = sizeof(aliasMdl_t);

	// Byte swap the header fields and sanity check
	inHeader = (md3Header_t *)data;

	id = LittleLong(inHeader->id);
	if (id != MD3_ID)
		Com_Error(false, "R_LoadMD3: wrong file id (%s)", name);

	version = LittleLong(inHeader->version);
	if (version != MD3_VERSION)
		Com_Error(false, "R_LoadMD3: wrong version number (%i should be %i) (%s)", version, MD3_VERSION, name);

	// Check the number of frames
	numFrames = LittleLong(inHeader->numFrames);
	if (numFrames < 1)
		Com_Error(false, "R_LoadMD3: no frames (%s)", name);
	else if (numFrames > MD3_MAX_FRAMES)
		Com_Error(false, "R_LoadMD3: too many frames (%s)", name);

	outModel->numFrames = numFrames;

	// Load the frames
	inFrame = (md3Frame_t *)((byte *)inHeader + LittleLong(inHeader->ofsFrames));

	outModel->frames = outFrame = (aliasMdlFrame_t *)Mem_Alloc(outModel->numFrames * sizeof(aliasMdlFrame_t), TAG_RENDERER);
	*size += outModel->numFrames * sizeof(aliasMdlFrame_t);

	for (i = 0; i < outModel->numFrames; i++, inFrame++, outFrame++){
		outFrame->bounds[0][i] = LittleFloat(inFrame->bounds[0][i]);
		outFrame->bounds[1][i] = LittleFloat(inFrame->bounds[1][i]);
	}

	// Check the number of tags
	numTags = LittleLong(inHeader->numTags);

	if (numTags < 0)
		Com_Error(false, "R_LoadMD3: no tags (%s)", name);
	else if (numTags > MD3_MAX_TAGS)
		Com_Error(false, "R_LoadMD3: too many tags (%s)", name);

	outModel->numTags = numTags;

	// Load the tags
	inTag = (md3Tag_t *)((byte *)inHeader + LittleLong(inHeader->ofsTags));

	outModel->tags = outTag = (aliasMdlTag_t *)Mem_Alloc(outModel->numFrames * outModel->numTags * sizeof(aliasMdlTag_t), TAG_RENDERER);
	*size += outModel->numFrames * outModel->numTags * sizeof(aliasMdlTag_t);

	for (i = 0; i < outModel->numFrames; i++){
		for (j = 0; j < outModel->numTags; j++, inTag++, outTag++){
			Str_Copy(outTag->name, inTag->name, sizeof(outTag->name));

			outTag->origin[0] = LittleFloat(inTag->origin[0]);
			outTag->origin[1] = LittleFloat(inTag->origin[1]);
			outTag->origin[2] = LittleFloat(inTag->origin[2]);

			outTag->axis[0][0] = LittleFloat(inTag->axis[0][0]);
			outTag->axis[0][1] = LittleFloat(inTag->axis[0][1]);
			outTag->axis[0][2] = LittleFloat(inTag->axis[0][2]);
			outTag->axis[1][0] = LittleFloat(inTag->axis[1][0]);
			outTag->axis[1][1] = LittleFloat(inTag->axis[1][1]);
			outTag->axis[1][2] = LittleFloat(inTag->axis[1][2]);
			outTag->axis[2][0] = LittleFloat(inTag->axis[2][0]);
			outTag->axis[2][1] = LittleFloat(inTag->axis[2][1]);
			outTag->axis[2][2] = LittleFloat(inTag->axis[2][2]);
		}
	}

	// Check the number of surfaces
	numSurfaces = LittleLong(inHeader->numSurfaces);
	if (numSurfaces < 1)
		Com_Error(false, "R_LoadMD3: no surfaces (%s)", name);
	else if (numSurfaces > MD3_MAX_SURFACES)
		Com_Error(false, "R_LoadMD3: too many surfaces (%s)", name);

	outModel->numSurfaces = numSurfaces;

	// Load the surfaces
	inSurface = (md3Surface_t *)((byte *)inHeader + LittleLong(inHeader->ofsSurfaces));

	outModel->surfaces = outSurface = (aliasMdlSurface_t *)Mem_Alloc(outModel->numSurfaces * sizeof(aliasMdlSurface_t), TAG_RENDERER);
	*size += outModel->numSurfaces * sizeof(aliasMdlSurface_t);

	for (i = 0; i < outModel->numSurfaces; i++, outSurface++){
		// Byte swap the header fields and sanity check
		id = LittleLong(inSurface->id);
		if (id != MD3_ID)
			Com_Error(false, "R_LoadMD3: wrong file id (%s)", name);

		// Check the number of materials
		numMaterials = LittleLong(inSurface->numMaterials);
		if (numMaterials < 1)
			Com_Error(false, "R_LoadMD3: no materials (%s)", name);
		else if (numMaterials > MD3_MAX_MATERIALS)
			Com_Error(false, "R_LoadMD3: too many materials (%s)", name);

		outSurface->numMaterials = numMaterials;

		// Load the materials
		inMaterial = (md3Material_t *)((byte *)inSurface + LittleLong(inSurface->ofsMaterials));

		outSurface->materials = outMaterial = (aliasMdlMaterial_t *)Mem_Alloc(outSurface->numMaterials * sizeof(aliasMdlMaterial_t), TAG_RENDERER);
		*size += outSurface->numMaterials * sizeof(aliasMdlMaterial_t);

		for (j = 0; j < outSurface->numMaterials; j++, inMaterial++, outMaterial++){
			Str_Copy(materialName, inMaterial->name, sizeof(materialName));
			Str_StripFileExtension(materialName);

			outMaterial->material = R_FindMaterial(materialName, MT_GENERIC, SURFACEPARM_NONE);
		}

		// Check the number of triangles
		numTriangles = LittleLong(inSurface->numTriangles);
		if (numTriangles < 1)
			Com_Error(false, "R_LoadMD3: no triangles (%s)", name);
		else if (numTriangles > MD3_MAX_TRIANGLES)
			Com_Error(false, "R_LoadMD3: too many triangles (%s)", name);

		outSurface->numTriangles = numTriangles;

		// Load the triangles
		inTriangle = (md3Triangle_t *)((byte *)inSurface + LittleLong(inSurface->ofsTriangles));

		outSurface->triangles = outTriangle = (aliasMdlTriangle_t *)Mem_Alloc(outSurface->numTriangles * sizeof(aliasMdlTriangle_t), TAG_RENDERER);
		*size += outSurface->numTriangles * sizeof(aliasMdlTriangle_t);

		for (j = 0; j < outSurface->numTriangles; j++, inTriangle++, outTriangle++){
			outTriangle->index[0] = (glIndex_t)LittleLong(inTriangle->index[0]);
			outTriangle->index[1] = (glIndex_t)LittleLong(inTriangle->index[1]);
			outTriangle->index[2] = (glIndex_t)LittleLong(inTriangle->index[2]);
		}

		// Check the number of vertices
		numVertices = LittleLong(inSurface->numVertices);
		if (numVertices < 1)
			Com_Error(false, "R_LoadMD3: no vertices (%s)", name);
		else if (numVertices > MD3_MAX_VERTICES)
			Com_Error(false, "R_LoadMD3: too many vertices (%s)", name);

		outSurface->numVertices = numVertices;

		// Load the S and T vertices
		inSt = (md3St_t *)((byte *)inSurface + LittleLong(inSurface->ofsSt));

		outSurface->st = outSt = (aliasMdlSt_t *)Mem_Alloc(outSurface->numVertices * sizeof(aliasMdlSt_t), TAG_RENDERER);
		*size += outSurface->numVertices * sizeof(aliasMdlSt_t);

		for (j = 0; j < outSurface->numVertices; j++, inSt++, outSt++){
			outSt->st[0] = LittleFloat(inSt->st[0]);
			outSt->st[1] = LittleFloat(inSt->st[1]);
		}

		// Allocate the face planes
		outSurface->facePlanes = (aliasMdlFacePlane_t *)Mem_Alloc(outSurface->numTriangles * sizeof(aliasMdlFacePlane_t), TAG_RENDERER);

		// Load the vertices
		inVertex = (md3Vertex_t *)((byte *)inSurface + LittleLong(inSurface->ofsVertices));

		outSurface->vertices = outVertex = (aliasMdlVertex_t *)Mem_Alloc(outModel->numFrames * outSurface->numVertices * sizeof(aliasMdlVertex_t), TAG_RENDERER);
		*size += outModel->numFrames * outSurface->numVertices * sizeof(aliasMdlVertex_t);

		for (j = 0; j < outModel->numFrames; j++, inFrame++){
			scale[0] = MD3_XYZ_SCALE;
			scale[1] = MD3_XYZ_SCALE;
			scale[2] = MD3_XYZ_SCALE;

			translate[0] = LittleFloat(inFrame->localOrigin[0]);
			translate[1] = LittleFloat(inFrame->localOrigin[1]);
			translate[2] = LittleFloat(inFrame->localOrigin[2]);

			for (k = 0; k < outSurface->numVertices; k++, inVertex++, outVertex++){
				outVertex->xyz[0] = (float)LittleShort(inVertex->xyz[0]) * scale[0] + translate[0];
				outVertex->xyz[1] = (float)LittleShort(inVertex->xyz[1]) * scale[1] + translate[1];
				outVertex->xyz[2] = (float)LittleShort(inVertex->xyz[2]) * scale[2] + translate[2];

				lat = (float)((inVertex->normal >> 8) & 0xFF) * M_PI / 128.0f;
				lng = (float)((inVertex->normal >> 0) & 0xFF) * M_PI / 128.0f;

				outVertex->normal[0] = glqMath::Sin(lng) * glqMath::Cos(lat);
				outVertex->normal[1] = glqMath::Sin(lng) * glqMath::Sin(lat);
				outVertex->normal[2] = glqMath::Cos(lng);
			}

			// Calculate tangent vectors
			R_CalcTangents(outSurface->numTriangles, outSurface->triangles, outSurface->numVertices, outSurface->vertices, outSurface->st, i);

			// Calculate face planes
			R_CalcFacePlanes(outSurface->numTriangles, outSurface->triangles, outSurface->vertices, outSurface->facePlanes);
		}

		// Clear index and vertex buffers
		outSurface->indexBuffer = NULL;
		outSurface->indexOffset = 0;

		outSurface->vertexBuffer = NULL;
		outSurface->vertexOffset = 0;

		// Go to the next surface
		inSurface = (md3Surface_t *)((byte *)inSurface + LittleLong(inSurface->ofsEnd));
	}

	// Free file data
	FS_FreeFile(data);

	// Try to cache the geometry in static index and vertex buffers
	R_CacheModelGeometry(outModel, name);

	return true;
}


/*
 ==============================================================================

 .SPR FORMAT LOADING

 ==============================================================================
*/


/*
 ==================

 ==================
*/
static bool R_LoadSPR (const char *name, sprMdl_t **sprite, int *size){

	byte			*data;
	sprHeader_t		*inHeader;
	sprMdl_t		*outSprite;
	int				id, version;

	// Load the file
	FS_ReadFile(name, (void **)&data);
	if (!data)
		return false;

	*sprite = outSprite = (sprMdl_t *)Mem_Alloc(sizeof(sprMdl_t), TAG_RENDERER);
	*size = sizeof(sprMdl_t);

	// Byte swap the header fields and sanity check
	inHeader = (sprHeader_t *)data;

	id = LittleLong(inHeader->id);
	if (id != SPR_ID)
		Com_Error(false, "R_LoadSPR: wrong file id (%s)", name);

	version = LittleLong(inHeader->version);
	if (version != SPR_VERSION)
		Com_Error(false, "R_LoadSPR: wrong version number (%i should be %i) (%s)", version, SPR_VERSION, name);

	// TODO!!!

	// Free file data
	FS_FreeFile(data);

	return true;
}


/*
 ==============================================================================

 MODEL LOADING

 ==============================================================================
*/


/*
 ==================
 R_LoadModel
 ==================
*/
static model_t *R_LoadModel (const char *name, modelType_t type, void *data, int size){

	model_t	*model;
	uint	hashKey;

	if (r_numModels == MAX_MODELS)
		Com_Error(false, "R_LoadModel: MAX_MODELS hit");

	r_models[r_numModels++] = model = (model_t *)Mem_Alloc(sizeof(model_t), TAG_RENDERER);

	// Fill it in
	Str_Copy(model->name, name, sizeof(model->name));
	model->type = type;
	model->data = data;
	model->size = size;

	// Add to hash table
	hashKey = Str_HashKey(model->name, MODELS_HASH_SIZE, false);

	model->nextHash = r_modelsHashTable[hashKey];
	r_modelsHashTable[hashKey] = model;

	return model;
}

/*
 ==================
 R_FindModel
 ==================
*/
model_t *R_FindModel (const char *name){

	model_t	*model;
	char	extension[MAX_PATH_LENGTH >> 4];
	void	*data;
	int		size;
	uint	hashKey;

	// See if already loaded
	hashKey = Str_HashKey(name, MODELS_HASH_SIZE, false);

	for (model = r_modelsHashTable[hashKey]; model; model = model->nextHash){
		if (!Str_ICompare(model->name, name))
			return model;
	}

	// Load it from disk
	Str_ExtractFileExtension(name, extension, sizeof(extension));

	if (!Str_ICompare(extension, ".mdl")){
		if (R_LoadMDL(name, (aliasMdl_t **)&data, &size))
			return R_LoadModel(name, MODEL_MDL, data, size);
	}

	if (!Str_ICompare(extension, ".md3")){
		if (R_LoadMD3(name, (aliasMdl_t **)&data, &size))
			return R_LoadModel(name, MODEL_MD3, data, size);
	}

	if (!Str_ICompare(extension, ".spr")){
		if (R_LoadSPR(name, (sprMdl_t **)&data, &size))
			return R_LoadModel(name, MODEL_SPRITE, data, size);
	}

	// Not found
	return NULL;
}

/*
 ==================
 R_RegisterModel
 ==================
*/
model_t *R_RegisterModel (const char *name){

	model_t	*model;

	model = R_FindModel(name);
	if (model)
		return model;

	// Register the name even if not found
	Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find model '%s', using default\n", name);

	return R_LoadModel(name, MODEL_BAD, NULL, 0);
}


// ============================================================================


/*
 ==================
 R_LoadInlineModel

 Used by the BSP loading code for inline models
 ==================
*/
void R_LoadInlineModel (int index, void *data){

	char	name[32];

	if (index == 0){
		R_LoadModel("_worldMap", MODEL_INLINE, data, 0);
		return;
	}

	Str_SPrintf(name, sizeof(name), "_inlineModel*%i", index);

	R_LoadModel(name, MODEL_INLINE, data, 0);
}

/*
 ==================
 R_ModelFrames
 ==================
*/
int R_ModelFrames (model_t *model){

	aliasMdl_t	*alias = (aliasMdl_t *)model->data;

	if (model->type != MODEL_MD3 && model->type != MODEL_MDL)
		return 0;

	return alias->numFrames;
}


/*
 ==============================================================================

 CONSOLE COMMANDS

 ==============================================================================
*/


/*
 ==================
 R_WorldMapInfo_f
 ==================
*/
static void R_WorldMapInfo_f (){

	if (!rg.world){
		Com_Printf("World map not loaded\n");
		return;
	}

	Com_Printf("\n");
	Com_Printf("%s\n", rg.world->name);
	Com_Printf("----------------------------------------\n");
	Com_Printf("%5i KB in %8i planes\n", SIZE_KB(rg.world->numPlanes * sizeof(glqPlane)), rg.world->numPlanes);
	Com_Printf("%5i KB in %8i textures\n", SIZE_KB(rg.world->numTextures * sizeof(texture_t)), rg.world->numTextures);
	Com_Printf("%5i KB in %8i vertices\n", SIZE_KB(rg.world->numVertices * sizeof(glVertex_t)), rg.world->numVertices);

	// TODO: PVS

	Com_Printf("%5i KB in %8i edges\n", SIZE_KB(rg.world->numEdges * sizeof(edge_t)), rg.world->numEdges);
	Com_Printf("%5i KB in %8i surface edges\n", SIZE_KB(rg.world->numSurfEdges * sizeof(int)), rg.world->numSurfEdges);
	Com_Printf("%5i KB in %8i texture informations\n", SIZE_KB(rg.world->numTexInfo * sizeof(texInfo_t)), rg.world->numTexInfo);
	Com_Printf("%5i KB in %8i surfaces\n", SIZE_KB(rg.world->numSurfaces * sizeof(surface_t)), rg.world->numSurfaces);
	Com_Printf("%5i KB in %8i marked surfaces\n", SIZE_KB(rg.world->numMarkSurfaces * sizeof(surface_t)), rg.world->numMarkSurfaces);
	Com_Printf("%5i KB in %8i leafs\n", SIZE_KB(rg.world->numLeafs * sizeof(leaf_t)), rg.world->numLeafs);
	Com_Printf("%5i KB in %8i nodes\n", SIZE_KB(rg.world->numNodes * sizeof(node_t)), rg.world->numNodes);
	Com_Printf("%5i KB in %8i inline models\n", SIZE_KB(rg.world->numInlineModels * sizeof(inlineModel_t)), rg.world->numInlineModels);
	Com_Printf("----------------------------------------\n");
	Com_Printf("%.2f MB of world map data\n", SIZE_MB_FLOAT(rg.world->size));
	Com_Printf("\n");
}

/*
 ==================
 
 TODO: MODEL_SPRITE
 FIXME: numIndices / 3 is not really correct since they are already triangles
 ==================
*/
static void R_ListModels_f (){

	model_t			*model;
	inlineModel_t	*inlineModel;
	aliasMdl_t		*mdlModel;
	int				numIndices, numVertices;
	int				bytes = 0;
	int				i, j;

	Com_Printf("\n");
	Com_Printf("      -srfs- -tris- -verts -size- -name-----------\n");

	for (i = 0; i < r_numModels; i++){
		model = r_models[i];

		bytes += model->size;

		Com_Printf("%4i: ", i);

		switch (model->type){
		case MODEL_INLINE:
			inlineModel = (inlineModel_t *)model->data;

			numIndices = numVertices = 0;

			for (j = 0; j < inlineModel->numSurfaces; j++){
				numIndices += inlineModel->surfaces[j].numTriangles;
				numVertices += inlineModel->surfaces[j].numVertices;
			}

			Com_Printf("%6i %6i %6i ", inlineModel->numSurfaces, numIndices / 3, numVertices);

			break;
		case MODEL_MDL:
		case MODEL_MD3:
			mdlModel = (aliasMdl_t *)model->data;

			numIndices = numVertices = 0;

			for (j = 0; j < mdlModel->numSurfaces; j++){
				numIndices += mdlModel->surfaces[j].numTriangles;
				numVertices += mdlModel->surfaces[j].numVertices;
			}

			Com_Printf("%6i %6i %6i ", mdlModel->numSurfaces, numIndices / 3, numVertices);

			break;
		case MODEL_SPRITE:

			break;
		default:
			Com_Printf("?????? ?????? ?????? ");

			break;
		}

		Com_Printf("%5ik ", SIZE_KB(model->size));

		Com_Printf("%s%s\n", model->name, (model->type == MODEL_BAD) ? " (DEFAULTED)" : "");
	}

	Com_Printf("--------------------------------------------------\n");
	Com_Printf("%i total models\n", r_numModels);
	Com_Printf("%.2f MB of model data\n", SIZE_MB_FLOAT(bytes));
	Com_Printf("\n");
}


/*
 ==============================================================================

 DEFAULT MODEL

 ==============================================================================
*/


/*
 ==================
 
 TODO: create a generic static model format? (MODEL_STATIC)
 ==================
*/
static void R_CreateDefaultModel (){

}


/*
 ==============================================================================

 INITIALIZATION & SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 R_InitModels
 ==================
*/
void R_InitModels (){

	// Add commands
	Cmd_AddCommand("worldMapInfo", R_WorldMapInfo_f, "Shows world map information", NULL);
	Cmd_AddCommand("listModels", R_ListModels_f, "Lists loaded models", NULL);

	// Create the default model
	R_CreateDefaultModel();
}

/*
 ==================
 R_ShutdownModels
 ==================
*/
void R_ShutdownModels (){

	// Remove commands
	Cmd_RemoveCommand("worldMapInfo");
	Cmd_RemoveCommand("listModels");

	// Clear model list
	Mem_Fill(r_modelsHashTable, 0, sizeof(r_modelsHashTable));
	Mem_Fill(r_models, 0, sizeof(r_models));

	r_numModels = 0;
}